package cn.erbudev.util;

import cn.erbudev.model.Arc;
import cn.erbudev.model.Line;
import cn.erbudev.model.Point;

import java.util.ArrayList;
import java.util.List;

/**
 * @author : xd_zhi_wang 22:45 2019/6/25 2019
 */
public class DrawUtil {
    public final static double Error = 0.000000001;

    /**
     * 求两个线段的交点
     *
     * @param line1 线段1
     * @param line2 线段2
     * @return 两个线段的交点
     */
    public static List<Point> getLine2LineCrossPoint(Line line1, Line line2) {
        double x;
        double y;
        double x1 = line1.getStartPoint().getX();
        double y1 = line1.getStartPoint().getY();
        double x2 = line1.getEndPoint().getX();
        double y2 = line1.getEndPoint().getY();
        double x3 = line2.getStartPoint().getX();
        double y3 = line2.getStartPoint().getY();
        double x4 = line2.getEndPoint().getX();
        double y4 = line2.getEndPoint().getY();
        double k1 = line1.getDenominator();
        double k2 = line2.getDenominator();
        boolean flag1 = Double.compare(k1, Double.MAX_VALUE) == 0;
        boolean flag2 = Double.compare(k2, Double.MAX_VALUE) == 0;

        //两个斜率相同，即平行无交点
        if (Math.abs(k1 - k2) < Error)
            return new ArrayList<>();

        if (flag1) {
            if (flag2)
                return new ArrayList<>();
            x = x1;
            if (k2 == 0) {
                y = y3;
            } else {
                y = k2 * (x - x4) + y4;
            }
        } else if (flag2) {
            x = x3;
            if (k1 == 0) {
                y = y1;
            } else {
                y = k1 * (x - x2) + y2;
            }
        } else {
            if (k1 == 0) {
                y = y1;
                x = (y - y4) / k2 + x4;
            } else if (k2 == 0) {
                y = y3;
                x = (y - y2) / k1 + x2;
            } else {
                x = (k1 * x2 - k2 * x4 + y4 - y2) / (k1 - k2);
                y = k1 * (x - x2) + y2;
            }
        }
        List<Point> result = new ArrayList<>();
        if (between(x1, x2, x) && between(y1, y2, y) && between(x3, x4, x) && between(y3, y4, y)) {
            result.add(new Point(x, y));
        }
        return result;
    }

    /**
     * 求线段与圆弧的交点
     *
     * @param line 线段
     * @param arc  圆弧
     * @return 线段与圆弧的交点
     */
    public static List<Point> getLine2ArcCrossPoint(Line line, Arc arc) {
        List<Point> resultList = new ArrayList<>();
        Point center = arc.getCentre();
        Point startLinePoint = line.getStartPoint();
        Point endLinePoint = line.getEndPoint();
        double centerX = center.getX();
        double centerY = center.getY();
        double k = line.getDenominator();
        double b = line.getIntercept();
        double d = Math.abs((k * centerX - centerY + b) / Math.sqrt(k * k + 1));
        double arcR = arc.getRadius();
        double A = 1 + k * k;
        double B = 2 * (k * (b - centerY) - centerX);
        double C = centerX * centerX + (b - centerY) * (b - centerY) - arcR * arcR;
        if ((d - arcR) > Error) {
            return resultList;
        } else if (Math.abs(d - arcR) < Error) { //相切，有一个交点
            double x = -B / (2 * A);
            double y = k * x + b;
            Point p = new Point(x, y);
            if (between(startLinePoint, endLinePoint, p)) {
                resultList.add(p);
            }
            return resultList;
        } else {    //两个交点在线段上
            double deta = Math.sqrt(B * B - 4 * A * C);
            double x1 = (-B + deta) / (2 * A);
            double y1 = k * x1 + b;
            double x2 = (-B - deta) / (2 * A);
            double y2 = k * x2 + b;
            Point p1 = new Point(x1, y1);
            Point p2 = new Point(x2, y2);
            if (between(startLinePoint, endLinePoint, p1) && between(startLinePoint, endLinePoint, p2)) {
                if (arc.isInnerPoint(p1)) {
                    if (arc.isInnerPoint(p2)) {//两个交点在圆弧上
                        resultList.add(p1);
                        resultList.add(p2);
                        return resultList;
                    } else {
                        //只有p1一个交点
                        resultList.add(p1);
                        return resultList;
                    }
                } else {
                    if (arc.isInnerPoint(p2)) {
                        //只有p2一个交点
                        resultList.add(p2);
                        return resultList;
                    }
                }
            } else if (between(startLinePoint, endLinePoint, p1) && !between(startLinePoint, endLinePoint, p2)) {
                if (arc.isInnerPoint(p1)) {
                    //只有p1一个交点
                    resultList.add(p1);
                    return resultList;
                }
            } else if (!between(startLinePoint, endLinePoint, p1) && between(startLinePoint, endLinePoint, p2)) {
                if (arc.isInnerPoint(p2)) {
                    //只有p1一个交点
                    resultList.add(p2);
                    return resultList;
                }
            }
        }
        return resultList;
    }

    /**
     * @param arc1
     * @param arc2
     * @return
     */
    public static List<Point> getArc2ArcCrossPoint(Arc arc1, Arc arc2) {
        List<Point> result = new ArrayList<Point>();
        double distance = arc1.getCentre().getDistance(arc2.getCentre());
        if (distance - (arc1.getRadius() + arc2.getRadius()) <= DrawUtil.Error) {
            List<Point> crossPoint = computeArc2ArcCrossPoint(arc1, arc2);
            if (crossPoint.size() != 0) {
                for (Point point : crossPoint) {
                    double angle1 = arc1.getAngle(arc1.getCentre(), point);
                    double angle2 = arc2.getAngle(arc2.getCentre(), point);
                    if (arc1.isInnerAngle(angle1) && arc2.isInnerAngle(angle2)) {
                        result.add(point);
                    }
                }
            }

        }
        return result;
    }

    /**
     * @param arc1
     * @param arc2
     * @return
     */
    private static List<Point> computeArc2ArcCrossPoint(Arc arc1, Arc arc2) {
        List<Point> result = new ArrayList<>();
        double x1 = arc1.getCentre().getX();
        double y1 = arc1.getCentre().getY();
        double x2 = arc2.getCentre().getX();
        double y2 = arc2.getCentre().getY();
        double r1 = arc1.getRadius();
        double r2 = arc2.getRadius();
        // 在一元二次方程中 a*x^2+b*x+c=0
        //x的两个根 x_1 , x_2
        //y的两个根 y_1 , y_2
        //如果 y1!=y2
        if (Math.abs(y1 - y2) > DrawUtil.Error) {
            //为了方便代入
            double A = (x1 * x1 - x2 * x2 + y1 * y1 - y2 * y2 + r2 * r2 - r1 * r1) / (2 * (y1 - y2));
            double B = (x1 - x2) / (y1 - y2);
            double a = 1 + B * B;
            double b = -2 * (x1 + (A - y1) * B);
            double c = x1 * x1 + (A - y1) * (A - y1) - r1 * r1;
            //下面使用判定式 判断是否有解
            double delta = b * b - 4 * a * c;
            if (delta > DrawUtil.Error) {
                double x_1 = (-b + Math.sqrt(delta)) / (2 * a);
                double x_2 = (-b - Math.sqrt(delta)) / (2 * a);
                double y_1 = A - B * x_1;
                double y_2 = A - B * x_2;
                result.add(new Point(x_1, y_1));
                result.add(new Point(x_2, y_2));
            } else if (Math.abs(delta) < DrawUtil.Error) {
                double x_1 = -b / (2 * a);
                double y_1 = A - B * x_1;
                result.add(new Point(x_1, y_1));
            }
        } else if (Math.abs(x1 - x2) > DrawUtil.Error) {

            //当y1=y2时，x的两个解相等
            double x_1 = (x1 * x1 - x2 * x2 + r2 * r2 - r1 * r1) / (2 * (x1 - x2));

            double a = 1;
            double b = -2 * y1;
            double c = y1 * y1 - r1 * r1 + (x_1 - x1) * (x_1 - x1);

            double delta = b * b - 4 * a * c;

            if (delta > DrawUtil.Error) {
                double y_1 = (-b + Math.sqrt(delta)) / (2 * a);
                double y_2 = (-b - Math.sqrt(delta)) / (2 * a);
                result.add(new Point(x_1, y_1));
                result.add(new Point(x_1, y_2));
            } else if (Math.abs(delta) < DrawUtil.Error) {
                double y_1 = -b / (2 * a);
                result.add(new Point(x_1, y_1));
            }
        }
        return result;
    }

    /**
     * @param a
     * @param b
     * @param target
     * @return
     */
    private static boolean between(double a, double b, double target) {
        return target >= a - Error && target <= b + Error || target <= a + Error && target >= b - Error;
    }

    /**
     * 判断目标点是否在点A与点B构造的线段上
     *
     * @param a      点A
     * @param b      点B
     * @param target 目标点
     * @return 判断结果
     */
    private static boolean between(Point a, Point b, Point target) {
        return (target.getX() >= a.getX() - Error && target.getX() <= b.getX() + Error || target.getX() <= a.getX() + Error && target.getX() >= b.getX() - Error);
    }

    /**
     * @param arc1
     * @param arc2
     * @param d
     * @return
     */
    public static List<Point> getArc2ArcIntersection(Arc arc1, Arc arc2, double d) {
        double startAngleArc1 = arc1.getStartAngle();
        double endAngleArc1 = arc1.getEndAngle();
        Point centerArc1 = arc1.getCentre();
        double rArc1 = arc1.getRadius();
        double startAngleArc2 = arc2.getStartAngle();
        double endAngleArc2 = arc2.getEndAngle();
        Point centerArc2 = arc2.getCentre();
        double rArc2 = arc2.getRadius();

        double startX1 = (rArc1 + d) * Math.cos(startAngleArc1) + centerArc1.getX();
        double startY1 = (rArc1 + d) * Math.sin(startAngleArc1) + centerArc1.getY();
        Point start1p = new Point(startX1, startY1);
        double endX1 = (rArc1 + d) * Math.cos(endAngleArc1) + centerArc1.getX();
        double endY1 = (rArc1 + d) * Math.sin(endAngleArc1) + centerArc1.getY();
        Point end1p = new Point(endX1, endY1);
        Arc newarc1 = new Arc(centerArc1, start1p, end1p);
        double startX2 = (rArc2 + d) * Math.cos(startAngleArc2) + centerArc2.getX();
        double startY2 = (rArc2 + d) * Math.sin(startAngleArc2) + centerArc2.getY();
        Point start2p = new Point(startX2, startY2);
        double endX2 = (rArc2 + d) * Math.cos(endAngleArc2) + centerArc2.getX();
        double endY2 = (rArc2 + d) * Math.sin(endAngleArc2) + centerArc2.getY();
        Point end2p = new Point(endX2, endY2);
        Arc newArc2 = new Arc(centerArc2, start2p, end2p);
        return getArc2ArcCrossPoint(newarc1, newArc2);
    }

    /**
     * 生成次级图元的交点
     *
     * @param line1 图元线段1
     * @param line2 图元线段2
     * @param d     线间距
     * @return 次级图元的交点
     */
    public static List<Point> getLineIntersection(Line line1, Line line2, double d) {
        List<Point> list = new ArrayList<>();
        boolean isDegreeConcave;    //角度是否是凹的，即是否大于180度
        double x1 = line1.getStartPoint().getX();
        double y1 = line1.getStartPoint().getY();
        double x2 = line1.getEndPoint().getX();
        double y2 = line1.getEndPoint().getY();
        double x3 = line2.getStartPoint().getX();
        double y3 = line2.getStartPoint().getY();
        double x4 = line2.getEndPoint().getX();
        double y4 = line2.getEndPoint().getY();
        //求线段长度
        double dist1_x = Math.abs(x2 - x1);
        double dist1_y = Math.abs(y2 - y1);
        double dist1 = Math.sqrt(dist1_x * dist1_x + dist1_y * dist1_y);
        double dist2_x = Math.abs(x3 - x4);
        double dist2_y = Math.abs(y3 - y4);
        double dist2 = Math.sqrt(dist2_x * dist2_x + dist2_y * dist2_y);
        double dx0 = x2 - x1;
        double dx1 = x4 - x3;
        double dy0 = y2 - y1;
        double dy1 = y4 - y3;

        //叉乘判断正负
        double cross = line2.getDirection().getX() * (line1.getDirection().getY()) - (-line1.getDirection().getX()) * (-line2.getDirection().getY());
        isDegreeConcave = (Double.compare(cross, Error) < 0);
        if (isDegreeConcave) {   //夹角大于180度时
            if ((Math.abs(dx0) > Error) && (Math.abs(dx1) > Error)) {
                Point p_l1 = DrawUtil.getPoint2LineProjection(line1.getEndPoint(), line1, d);
                Point p_l2 = DrawUtil.getPoint2LineProjection(line2.getStartPoint(), line2, d);
                list.add(line1.getEndPoint());
                list.add(p_l1);
                list.add(p_l2);
            } else if ((Math.abs(dx0) <= Error) && (Math.abs(dx1) > Error)) {
                Point p_l1 = dy0 > Error ? new Point(x2 + d, y2) : new Point(x2 - d, y2);
                Point p_l2 = DrawUtil.getPoint2LineProjection(line2.getStartPoint(), line2, d);
                list.add(line1.getEndPoint());
                list.add(p_l1);
                list.add(p_l2);
            } else if ((Math.abs(dx1) <= Error) && (Math.abs(dx0) > Error)) {
                Point p_l2 = dy1 > Error ? new Point(x3 + d, y3) : new Point(x3 - d, y3);
                Point p_l1 = DrawUtil.getPoint2LineProjection(line1.getEndPoint(), line1, d);
                list.add(line1.getEndPoint());
                list.add(p_l1);
                list.add(p_l2);
            }

        } else { //夹角小于180度

            if ((Math.abs(dx0) > Error) && (Math.abs(dx1) > Error)) {
                double k0 = (y2 - y1) / dx0;
                double k1 = (y4 - y3) / dx1;
                double x = (dx0 * x4 * y3 - dx0 * x3 * y4 - dx0 * d * dist2 - dx1 * x2 * y1
                        + dx1 * x1 * y2 + dx1 * d * dist1) / (dx0 * dx1 * (k0 - k1));
//                double y = (k0 * dx0 * x4 * y2 - k0 * dx0 * x2 * y4 - k0 * dx0 * d * dist2
//                        - dx1 * x2 * y1 * k1 + dx1 * x1 * y2 * k1 + dx1 * d * dist1 * k1) / (dx0 * dx1 * (k0 - k1));
                double y = k0 * x + (x2 * y1 - x1 * y2 - d * dist1) / dx0;
                Point p = new Point(x, y);
                list.add(p);
            } else if ((Math.abs(dx0) <= Error) && (Math.abs(dx1) > Error)) {
                double k1 = (y4 - y3) / dx1;
                double x;
                double y;
                if (dy0 > Error) {
                    x = x2 + d;
//                    y = -(x2 * y3 - k1 * dx1 * x2 - k1 * dx1 * d - x3 * y2 + d * dist2) / dx1;
                    y = k1 * x + (x4 * y3 - x3 * y4 - d * dist2) / dx1;
                } else {
                    x = x2 - d;
//                    y = -(x2 * y3 - k1 * dx1 * x2 + k1 * dx1 * d - x3 * y2 + d * dist2) / dx1;
                    y = k1 * x + (x4 * y3 - x3 * y4 - d * dist2) / dx1;
                }
                Point p = new Point(x, y);
                list.add(p);
            } else if ((Math.abs(dx1) <= Error) && (Math.abs(dx0) > Error)) {
                double k0 = (y2 - y1) / dx0;
                double x;
                double y;
                if (dy1 > 0) {
                    x = x3 + d;
//                    y = -(x1 * y2 - k0 * dx0 * x2 - k0 * dx0 * d - x2 * y1 + d * dist1) / dx0;
                    y = k0 * x + (x2 * y1 - x1 * y2 - d * dist1) / dx0;
                } else {
                    x = x3 - d;
//                    y = -(x1 * y2 - k0 * dx0 * x2 + k0 * dx0 * d - x2 * y1 + d * dist1) / dx0;
                    y = k0 * x + (x2 * y1 - x1 * y2 - d * dist1) / dx0;
                }
                Point p = new Point(x, y);
                list.add(p);
            }
        }
        return list;
    }

    /**
     * @param l1
     * @param arc
     * @param d
     * @return
     */
    public static List<Point> getLine2ArcIntersection(Line l1, Arc arc, double d) {
        //        直线l1
        double k = l1.getDenominator();
        boolean flag = k == Double.MAX_VALUE;
        double x1 = l1.getStartPoint().getX();
        double y1 = l1.getStartPoint().getY();
        double x2 = l1.getEndPoint().getX();
        double y2 = l1.getEndPoint().getY();

//        弧线arc
        double c1 = arc.getCentre().getX();
        double c2 = arc.getCentre().getY();
        double r = arc.getRadius() + d;

//        新直线Ax+By+C = 0
        double A, B, C;
        if (flag) {
//            x = x1  ==>  x = x1 + or - d     B == 0    x = -C/A
            C = -(x1 + ((y2 - y1) / Math.abs(y2 - y1)) * d);
//            求交点 (x1,y_1)   (x1,y_2)

            double delta = Math.pow(r, 2) - Math.pow(C + c1, 2);
            if (Math.abs(delta) < DrawUtil.Error) delta = 0.0;
            double y_1 = c2 + Math.sqrt(delta);
            double y_2 = c2 - Math.sqrt(delta);

            if (y_1 == y_2) {
                List<Point> result = new ArrayList<Point>();
                result.add(new Point(-C, y_1));
                return result;
            }
//            取正确交点(x,y) , 向量a
            else {
                Point p1 = new Point(-C, y_1);
                Point p2 = new Point(-C, y_2);

                Point e = getPoint2LineProjection(l1.getEndPoint(), l1, d);
                Point s = getPoint2LineProjection(l1.getStartPoint(), l1, d);
                double d1 = p1.getDistance(s) + p1.getDistance(e);
                double d2 = p2.getDistance(s) + p2.getDistance(e);
                List<Point> result = new ArrayList<>();
                result.add(d1 > d2 ? new Point(-C, y_2) : new Point(-C, y_1));
                return result;

            }
        } else {
//            Ax+By+C = 0                      B != 0
            A = (y2 - y1) / (x2 - x1);
            B = -1;
            C = (x2 * y1 - x1 * y2) / (x2 - x1) - d * (Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))) / (x2 - x1);
            double a, b, c, delta;
            a = (1 + Math.pow(A / B, 2));
            b = 2 * A * C / Math.pow(B, 2) + 2 * A * c2 / B - 2 * c1;
            c = Math.pow(c1, 2) + Math.pow(c2, 2) - Math.pow(r, 2) + Math.pow(C, 2) / Math.pow(B, 2) + 2 * c2 * C / B;
            delta = Math.pow(b, 2) - 4 * a * c;
//            一个交点(x,y)
            if (Math.abs(delta) < Error) {
//                System.out.println("delta == 0");

                double x = -b / (2 * a);
                double y = A * x + C;
                List<Point> result = new ArrayList<Point>();
                result.add(new Point(x, y));
                return result;

            }
//            两个交点(x_1,y_1) (x_2,y_2)
            else {
                double x_1, x_2, y_1, y_2;
                x_1 = (-b + Math.sqrt(delta)) / (2 * a);
                x_2 = (-b - Math.sqrt(delta)) / (2 * a);
                y_1 = A * x_1 + C;
                y_2 = A * x_2 + C;
                Point p1 = new Point(x_1, y_1);
                Point p2 = new Point(x_2, y_2);
                Point e = getPoint2LineProjection(l1.getEndPoint(), l1, d);
                Point s = getPoint2LineProjection(l1.getStartPoint(), l1, d);
                double d1 = p1.getDistance(s) + p1.getDistance(e);
                double d2 = p2.getDistance(s) + p2.getDistance(e);
                List<Point> result = new ArrayList<>();
                result.add(d1 > d2 ? new Point(x_2, y_2) : new Point(x_1, y_1));
                return result;
            }
        }
    }

    /**
     * 求得顶点p在轮廓线line产生的下一层图元直线上的投影
     *
     * @param p
     * @param line
     * @param d
     * @return
     */
    private static Point getPoint2LineProjection(Point p, Line line, double d) {
        double x0 = p.getX();
        double y0 = p.getY();
        double x1 = line.getStartPoint().getX();
        double y1 = line.getStartPoint().getY();
        double x2 = line.getEndPoint().getX();
        double y2 = line.getEndPoint().getY();
        double dist_x = Math.abs(x2 - x1);
        double dist_y = Math.abs(y2 - y1);
        double dist = Math.sqrt(dist_x * dist_x + dist_y * dist_y);
        double y1_line = (x2 * y1 - x1 * y2 - d * dist) / (x2 - x1);//直线l上的点（0,y1）
        double y2_line = (y2 - y1) / (x2 - x1) + y1_line;//直线l上的点（1,y2）
        double k_line = (x0 + y2_line * y0 - y1_line * y0 - y1_line * y2_line + y1_line * y1_line) / (1 + (y2_line - y1_line) * (y2_line - y1_line));
        double y = k_line * y2_line + (1 - k_line) * y1_line;
        return new Point(k_line, y);
    }

}
